Project Detail

Number Guessing Game

I built this as the Day 12 project for 100 Days of Code, where the focus was Python scope and namespaces. The game picks a random number between 1 and 100 and gives you Easy (10 turns) or Hard (5 turns) to guess it — simple premise, but I used it as an excuse to build something properly rather than just get it working. The repo ships two versions side by side: the original close to the course solution, and a rebuilt OOP version I'm actually proud of.

Software CLI game OOP python terminal terminal-ux scope dataclasses

Quick Facts

Tech:
Python

Overview

Problem

This project was my first dive in to the concept of scope — the LEGB rule, why the global keyword is a trap, and why Python doesn't have block scope like most other languages. The problem with just doing the exercise as given is that you can write a working solution while completely missing the point. I wanted to build something that made those concepts visible in the structure of the code itself, not just in a comment explaining what the lesson was about.

Solution

I built two versions so the contrast is concrete and inspectable. The original stays close to the course — procedural, one file, global constants in ALL_CAPS, helper functions that return values instead of mutating globals. The advanced version enforces one hard rule: the game logic layer (a GameState dataclass) has zero UI imports. display.py owns every print() call. main.py only orchestrates — collect input, call logic, call display. GuessResult and Difficulty enums replace every magic string and bare boolean, and a shared menu.py at the root launches either version via subprocess.

Challenges

The trickiest design decision was making the zero-UI-imports rule actually work. The solution was GuessResult as an enum — GameState.guess() returns a value, main.py reads it and decides what to show. The logic layer never needs to know a terminal exists. A less obvious problem was ASCII art in Python triple-quoted strings: a backslash at the end of a line is silently treated as a line continuation, so lines were merging with no error and no warning. I had to double every backslash in the logo string to fix it. Getting raw keypress detection working for the post-game navigation (up arrow, Enter, q without needing Enter) also required dropping into tty/termios to bypass Python's line-buffered input.

Results / Metrics

The project does what I wanted — the architecture makes the scope concepts tangible rather than just described. Seeing the two versions next to each other makes it obvious why passing data in and returning it out is cleaner than reaching into global state. The thing I'm most happy with is the GuessResult enum design: it kept the logic layer genuinely portable and made main.py read almost like pseudocode. If I were to extend it, I'd add a high score tracker and pull the file I/O into its own module — the architecture is already set up for that cleanly.

Screenshots

Click to enlarge.

Click to enlarge.

Videos